///////////////////////////////////////////////////////////////////////////////
/// @file server_object.cpp
/// @author -- Benjamin J. Orr
/// @brief -- Server class for chat program
///////////////////////////////////////////////////////////////////////////////

/////////////////////////////
/// CUSTOM LIBS
/////////////////////////////
#include "server_object.h"
#include "../lib/string_manipulation.h"
#include "../lib/debug.h"
#include "../lib/library_generic.h"

/////////////////////////////
/// LIBS
/////////////////////////////
#include <string>
using std::string;
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
#include <signal.h> //signal()
#include <cstring> //memset()
#include <signal.h> //signal constants
#include <unistd.h> //STDIN_FILENO
#include <limits.h> //UINT_MAX
#include <errno.h> //errno, not used at the moment
#include <pthread.h> //mutexes

//network libraries
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>

/////////////////////////////
/// CONSTS
/////////////////////////////
string QUIT = "/quit";
string EXIT = "/exit";
string PART = "/part";
string NICK = "/nick";
string SYSTEM = "ADMINISTRATOR: ";
string COMMAND = "/";
int MAX_CLIENTS = 10;
int QUITTIME = 6;
int BUFFER_SIZE = 1024;
int SERVER = -1;

//When we call Server:: thread_read() we need two pieces of information to wrap
// read(). A pointer to the Server object that called the thread, and the
// the socket we need to read from. This structure takes care of that.
// server is set to this, and socket is set to the socket file descriptor
// of the socket that the thread is to read from.
struct ThreadSocketInfo
{
  Server * server;
  int socket;
};

///////////////////////////////////////////////////////////////////////////////
/// @fn Server()
/// @brief Constructor for Server object
/// @pre None!
/// @post Creates object Server
/// @return N/A
///////////////////////////////////////////////////////////////////////////////
Server::Server()
{
  debug(9, "Server::Server()");  
  signal(SIGINT, signal_interceptor);
  //signal(SIGQUIT, signal_interceptor);
  //signal(SIGTERM, signal_interceptor);
  m_running = true;
  pthread_mutex_init(&m_client_var_lock, NULL);
}

///////////////////////////////////////////////////////////////////////////////
/// @fn ~Server()
/// @brief Destructor for Server object
/// @pre Gotta have one to get rid of one
/// @post You've got fewer Server objects
/// @return N/A
///////////////////////////////////////////////////////////////////////////////
Server::~Server()
{
  debug(9, "Server::~Server()");
  //Nothing to see here
}

///////////////////////////////////////////////////////////////////////////////
/// @fn init()
/// @brief Initializes network communication
/// @pre A Server object must exist
/// @param The port where the server will be communicating
/// @post The server is ready to communicate with a client
/// @return void
///////////////////////////////////////////////////////////////////////////////
void Server::init(int port)
{
  debug(9,"Server::init()");
  struct addrinfo hints;
  int status = 0;
  
  //empty hints
  memset(&hints, 0, sizeof hints);

  //stuff hints
  hints.ai_family = AF_INET;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE;
  
  //use getaddrinfo to fill up res for us
  status = getaddrinfo(NULL, int_to_str(port).c_str(), &hints, &m_res);
  debug(8,"Server::init() -- getaddrinfo()");
  if(status != 0)
  {
    cerr << "Failed to retrieve address info. Server::init()" << endl;
  }
  
  //get a socket descriptor
  m_sock_fd = socket(m_res->ai_family, m_res->ai_socktype, m_res->ai_protocol);
  debug(8,"Server::init() -- socket()");
  if(m_sock_fd == -1)
  {
    cerr << "Failed to retrieve socket from system. Server::init()" << endl;
  }
  
  //attempt to bind your socket (sock_fd) to a port
  status = bind(m_sock_fd, m_res->ai_addr, m_res->ai_addrlen);
  debug(8,"Server::init() -- bind");
  if(status != 0)
  {
    cerr << "Failed to bind socket to port. Errno: " << errno  << " Server::init()" << endl;
  }
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void signal_interceptor(int sig)
/// @brief The handles signals
/// @pre A Server object must exist
/// @param int sig -- Signal to be intercepted
/// @post The signal is handled correctly
/// @return N/A
///////////////////////////////////////////////////////////////////////////////
void Server::signal_interceptor(int sig)
{
  debug(9,"Server::signal_interceptor()");

/*let's just ignore the signal handler until we get the quit functions working

  if(sig == SIGINT || sig == SIGTERM || sig == SIGQUIT)
  {
    cout << "Unable to comply. Use defined commands for clean shutdown:" << endl;
    cout << "Try: " << QUIT << ", " << EXIT << ", or " << PART << endl;
  }

*/
  return;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void thread_listen() 
/// @brief Wraps void Server::listen for threads to use
/// @pre A Server object exists and is running
/// @post Wraps
/// @return NULL
///////////////////////////////////////////////////////////////////////////////
void * Server::thread_listener(void * serv)
{
  debug(8,"Server::thread_listener()");
  Server * server_ptr = (Server *)serv;
  server_ptr->listener();
  return NULL;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void listen()
/// @brief Listens for connections and generates threads to handle them
/// @pre A Server object has been created and initialized
/// @param none
/// @post Threads are generated by this function for each connection
/// @return N/A
///////////////////////////////////////////////////////////////////////////////
void Server::listener()
{
  debug(8,"Server::listener()");

  while(m_running)
  {
    //As long as we don't have too many connections already we can add some more
    if(m_client_sockets.size() < MAX_CLIENTS)
    {
      //set some temp variables for this goarround
      sockaddr_in client_address;
      client_address.sin_family = AF_INET;
      int client_length = sizeof(client_address);

      //accept an incoming client if there is one, client_socket
      // will make a good fd for now
      int client_socket = accept(m_sock_fd, (sockaddr *)&client_address, (socklen_t *)&client_length);
      if(client_socket == -1)
      {
        cerr << "Failed to accept connection. Server::listener()" << endl;
      }

      //lockout changes to the vectors while we update them
      // This is to prevent a read/write collision with garbage_man()
      pthread_mutex_lock(&m_client_var_lock);
      {     

        //put the client in the addresses
        m_client_addresses.push_back(client_address);
        //put the client in the sockets
        m_client_sockets.push_back(client_socket);
      
        //give the client a long and unique username until they change it
        set_username(client_socket, "Guest" + int_to_str(client_socket));

        //preparation for a new thread
        int status = 0;
        pthread_t client_thread;

        //We need to pass two pieces of information to thread_read(), so we pass
        // it a struct containing both. A pointer to the calling class, and the
        // latest client to be pushed on to m_client_sockets
        ThreadSocketInfo read_data;
        read_data.server = this;
        read_data.socket = m_client_sockets[m_client_sockets.size()-1];

        //spawn a new thread, just for the client
        status = pthread_create(&client_thread, NULL, Server::thread_read, &read_data);
        if(status != 0)
        {
          cerr << "Failed to spawn new client thread. Server::listener()" << endl;
        }

        //put the thread in a box before we do it all over again
        m_thread_clients.push_back(client_thread);
        
      //unlock client vectors
      }pthread_mutex_unlock(&m_client_var_lock);
      
      string notif = "";

      notif = get_username(client_socket) + " has joined the server. Play nice. \n";

      write_out(SERVER, notif);
    }
  }
  return;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void thread_input()
/// @brief Wraps void Server::input() for threads to use
/// @pre A Server object must exist and be initialized
/// @post Wraps
/// @return N/A
///////////////////////////////////////////////////////////////////////////////
void * Server::thread_input(void * serv)
{
  debug(8,"Server::thread_input()");
  Server * server_ptr = (Server *)serv;
  server_ptr->input();
  return NULL;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void input();
/// @brief Reads from stdin and routes messages to either command handlers
///        or the chatroom
/// @pre A Server object must exist and be initiated,
///      an input thread must be spawned
/// @post Your words be done
/// @return N/A
///////////////////////////////////////////////////////////////////////////////
void Server::input()
{
  debug(8,"Server::input()");
  string input;
  int status = 0;

  while(m_running)
  {
    input.erase(input.begin(), input.end());
    getline(cin, input);

    //First, we handle quit statements
    if(!(input.compare(EXIT) && input.compare(QUIT) && input.compare(PART)))
    {
      input = "Chat Server is going down in " + int_to_str(QUITTIME) + " seconds";
      status = pthread_create(&m_thread_pause, NULL, Server::thread_wait_to_die, this);
      if(status == -1)
      {
        //error so we quit without warning
        cerr << "Failed to spawn timeout thread. Server::input()" << endl;
      }
    }

    //If the first character is a slash, we'll check for a valid command
    if(!input.compare(0,1,COMMAND))
    {
      parse_command(SERVER,input);
    }

    //must be a message for the chat room
    write_out(SERVER,input);
  }
  return;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void run()
/// @brief Starts a Server object
/// @pre A Server object must exist and be initialized
/// @param none
/// @post The chat server spawns a listener thread and input thread
/// @return N/A
///////////////////////////////////////////////////////////////////////////////
void Server::run()
{
  debug(8, "Server::run()");
  int status = 0;
 
  //listen for incoming connections
  status = listen(m_sock_fd, MAX_CLIENTS);
  if(status == -1)
  {
    cerr << "Failed to listen. Server::run()" << endl;
  }
  cout << endl;
  cout << SYSTEM << "Chat server started!" << endl;
  cout << SYSTEM << "Type /exit /quit or /part to stop server." << endl;
  cout << endl;

  //Create two threads, one for listening (which in turn spawns more threads)
  // and another which you can use to write to the chat room as the server

  status = pthread_create(&m_thread_listener, NULL, Server::thread_listener, this);
  if(status == -1)
  {
    cerr << "Failed to create listener thread. Server::run()" << endl;
  }

  status = pthread_create(&m_thread_input, NULL, Server::thread_input, this);
  if(status == -1)
  {
    cerr << "Failed to create input thread. Server::run()" << endl;
  }

  status = pthread_join(m_thread_input, NULL);
  if(status == -1)
  {
    cerr << "Failed to reunite m_thread_input. Server::run()" << endl;
  }

  status = pthread_join(m_thread_listener, NULL);
  if(status == -1)
  {
    cerr << "Failed to reunite m_thread_listener. Server::run()" << endl;
  }

  return;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void parse_command(int socket,string cmd)
/// @brief Parses commands from both the server and the clients, NOTE: this
///        function may be called erroniously, that's fine. Commands can be
///        added in the future, all commands are of the format: /**** with
///        an optional argument.
/// @pre A Server object must exist and be initiated
/// @post Commands to the server are handled
/// @param int socket -- The source of the command
/// @param string cmd -- The string to be compared to known commands
/// @return N/A
///////////////////////////////////////////////////////////////////////////////
void Server::parse_command(int socket,string cmd)
{
  debug(8,"Server::parse_command()");
  string keyword, argument;

  keyword = cmd.substr(0,5);
  //we need to check for an argument, otherwise substr gets cranky
  if(cmd.length() > 5)
  {
    argument = cmd.substr(6,cmd.length()-6);
  }

  //just check the available commands and run the appropriate function
  if(keyword == QUIT || keyword == EXIT || keyword == PART)
  {
    garbage_man(socket);
  }
  if(keyword == NICK)
  {
    if(socket == SERVER)
    {
      cout << "The server's name cannot be changed" << endl;
    }
    else
    {
      set_username(socket,argument);
    }
  }

  return;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void thread_wait_to_die()
/// @brief A wrapper for wait_to_die
/// @pre A Server object must exist and be initiated
/// @post Wraps
/// @return NULL
///////////////////////////////////////////////////////////////////////////////
void * Server::thread_wait_to_die(void *serv)
{
  debug(8,"Server::thread_wait_to_die()");
  Server * server_ptr = (Server *)serv;
  server_ptr->wait_to_die();
  return NULL;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void wait_to_die()
/// @brief A shutdown timer thread for the server
/// @pre You WANT the server to shut down
/// @post The server IS shut down (by way of m_running)
/// @return N/A
///////////////////////////////////////////////////////////////////////////////
void Server::wait_to_die()
{
  debug(8,"Server::wait_to_die()");
  sleep(QUITTIME/2);
  cout << SYSTEM << "Chat Server is going down in " << QUITTIME/2 << " seconds" << endl;
  sleep((QUITTIME/2)-1);
  cout << SYSTEM << "Goodbye!" << endl;
  sleep(1);
  pthread_kill(m_thread_input,SIGTERM);
  return;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void set_username(const int socket, const string &username)
/// @brief Sets username of the client specified by the socket
/// @pre A Server object exists and has been initiated
/// @pre A Client is connected
/// @param const int socket -- The socket identifier of the user
/// @param const string &username -- The new username
/// @post See brief
/// @return None
///////////////////////////////////////////////////////////////////////////////
void Server::set_username(const int socket, const string &username)
{
  debug(8,"Server::set_username()");
  int index;
  string echo = "";

  //quick and dirty inline search function, no error checking
  // if you're calling set_username you should know what you're doing
  // after all this organization and error checking I've been doing
  // this feels pretty awful
  for(int i = 0; i < m_client_sockets.size(); i++)
  {
    if(m_client_sockets[i] == socket)
    {    
      index = i;
      break;
    }
  }

  //Check to see if the client has an assigned socket and an assigned
  // username. If so this is a rename, not a christening.
  if(m_client_usernames.size() == m_client_sockets.size())
  {
    echo += "User: " + m_client_usernames[index] + " is now known as: " + username;
    write_out(SERVER,echo);
  }

  if(index >= m_client_usernames.size())
  {
    m_client_usernames.resize(index + 1);
  }
  m_client_usernames[index] = username;
  
  return;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn string get_username(int sock)
/// @brief Retrieves the username from m_client_usernames
/// @pre A Server object must exist and have been initiated
/// @pre There must be at least one client
/// @param int sock -- The socket number of the user
/// @return The string 'username'
///////////////////////////////////////////////////////////////////////////////
string Server::get_username(int socket)
{
  debug(8,"Server::get_username()");
  int index;

  //yeah, this is the second time... *sighs*
  for(int i = 0; i < m_client_sockets.size(); i++)
  {
    if(m_client_sockets[i] == socket)
    {
      index = i;
      break;
    }
  }
  return m_client_usernames[index];
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void * thread_read(void * data)
/// @brief Wraps void Server::read()
/// @pre A Server object exists and is running
/// @post Wraps
/// @return NULL
///////////////////////////////////////////////////////////////////////////////
void * Server::thread_read(void * data)
{
  debug(8,"Server::thread_read()");
  ThreadSocketInfo read_data;
  int status = 0;
  bool go = true;

  read_data = *(ThreadSocketInfo *)data;

  while(search(read_data.server->m_client_sockets, read_data.socket) != UINT_MAX)
  {
    read_data.server->read_in(read_data.socket);
  }

  return NULL;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void read_in(int socket)
/// @brief Allows the server to read from its clients
/// @pre A Server object must exist and be initiated
/// @param int socket -- The socket that's to be read from
/// @return N/A
///////////////////////////////////////////////////////////////////////////////
void Server::read_in(int socket)
{
  debug(8,"Server::read_in()");

  char buffer[BUFFER_SIZE]; 
  string msg;

  memset(buffer,'\0',BUFFER_SIZE);

  cout << "1" << endl;
  cout << "Buffer: " << buffer << endl;
  cout << "Msg: " << msg << endl;

  int status = 0;

  status = recv(socket, buffer, BUFFER_SIZE-1, 0);
  if(status == -1)
  {
    cerr << "Failed to read from buffer. Server::read_in()" << endl;
  }

  cout << "2" << endl;
  cout << "Buffer: " << buffer << endl;
  cout << "Msg: " << msg << endl;

  if(status == 0)
  {
    //This should be handled by removing the client's entry in 
    // associated vectors. In the future garbage_man() will do it
    cerr << "Failed to read from the buffer, client disconnected. Server::read_in()" << endl;
    garbage_man(socket);
  }
  if(status > BUFFER_SIZE)
  {
    //optional:
    //Inform the sender that their message was truncated to BUFFER_MAX
  }

  cout << "3" << endl;
  cout << "Buffer: " << buffer << endl;
  cout << "Msg: " << msg << endl;

  msg = buffer;
  //memset(&buffer,0,BUFFER_SIZE);

  cout << "4" << endl;
  cout << "Buffer: " << buffer << endl;
  cout << "Msg: " << msg << endl;

  if(buffer[0] == '/' && buffer[1] != '/')
  {
    //so if it begins with one slash we don't echo it to the screen
    parse_command(socket,msg);
  }
  else
  {
    write_out(socket, msg);
  }

  return;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void write_out(int socket, string msg)
/// @brief Outputs a message to the entire chat room
/// @pre A Server object exists and is initiated
/// @param int socket -- The socket file descriptor of the message source
/// @param string msg -- The message to be sent to the chat room
/// @post Information is shared among clients!
///////////////////////////////////////////////////////////////////////////////
void Server::write_out(int socket, string msg)
{
  debug(8,"Server::write_out()");

  string output;
  int status, exclude;
  status = 0;

  cout << msg << endl;

  if(socket == SERVER)
  {
    output = SYSTEM + msg;
  }
  else
  {
    output = get_username(socket) + ": " + msg;
  }

  //The user that we aren't sending to
  exclude = search(m_client_sockets, socket);
  
  //We could spam EVERYBODY! (except exclude)
  for(int i = 0; i < m_client_sockets.size(); i++)
  {
    if(i != exclude)
    {
      status = send(m_client_sockets[i], output.c_str(), output.size(), 0);
    }
  }
  return;
}

///////////////////////////////////////////////////////////////////////////////
/// @fn void garbage_man(int socket)
/// @brief Removes the cliend indicated by socket from the various lists
///        of client vectors
/// @pre A Server object must exist with at least one client
/// @param int socket -- The socket that describes the client who is to be
///        removed
/// @post The client is removed from the list and its threads are shut down
/// @return N/A
///////////////////////////////////////////////////////////////////////////////
void Server::garbage_man(int socket)
{
  debug(8,"Server::garbage_man()");
  string message = "";
  int location;

  //lockout changes
  pthread_mutex_lock(&m_client_var_lock);
  {
    //get the index
    location = search(m_client_sockets,socket);
    message += "User: " + m_client_usernames[location] + " is no longer with us.";
    //remove the user from our vectors
    m_client_sockets.erase(m_client_sockets.begin() + location);
    m_client_usernames.erase(m_client_usernames.begin() + location);
    m_client_addresses.erase(m_client_addresses.begin() + location);
    //notify the room
    write_out(SERVER,message);
  //return control to other functions (like listen)
  }pthread_mutex_unlock(&m_client_var_lock);
  return;
}
